import socket
import threading
import json
import struct
import select
import usb.core
import usb.util
import usb.backend.libusb1
import bitstruct
import time
HOST = ''
PORT = 55355
EP2_BUF_SIZE = 1024
CHA_FLAG = 0x100ffff
CHB_FLAG = 0x200ffff
POWER_ON_OFF = 0x11
GET_EP2468STAT = 0x12
GET_PWR_STATUS = 0x13
FPGA_GLOBAL_RST = 0x14
RST_EP2_BUF = 0x15
RST_EP8_BUF = 0x16
GET_SOC_STAT = 0x17
FPGA_GLOBAL_RST_ACTIVE = 0x18
FPGA_GLOBAL_RST_INACTIVE = 0x19
PWR_SWITCH_REQ = usb.util.build_request_type(usb.util.CTRL_OUT,usb.util.CTRL_TYPE_VENDOR,usb.util.CTRL_RECIPIENT_DEVICE)
EPSTAT_REQ = usb.util.build_request_type(usb.util.CTRL_IN,usb.util.CTRL_TYPE_VENDOR,usb.util.CTRL_RECIPIENT_DEVICE)
PWR_STATUS_REQ = usb.util.build_request_type(usb.util.CTRL_IN,usb.util.CTRL_TYPE_VENDOR,usb.util.CTRL_RECIPIENT_DEVICE)
FPGA_GLOBAL_RST_REQ = usb.util.build_request_type(usb.util.CTRL_OUT,usb.util.CTRL_TYPE_VENDOR,usb.util.CTRL_RECIPIENT_DEVICE)
RST_EP2_BUF_REQ = usb.util.build_request_type(usb.util.CTRL_OUT,usb.util.CTRL_TYPE_VENDOR,usb.util.CTRL_RECIPIENT_DEVICE)
RST_EP8_BUF_REQ = usb.util.build_request_type(usb.util.CTRL_OUT,usb.util.CTRL_TYPE_VENDOR,usb.util.CTRL_RECIPIENT_DEVICE)
SOC_STAT_REQ = usb.util.build_request_type(usb.util.CTRL_IN,usb.util.CTRL_TYPE_VENDOR,usb.util.CTRL_RECIPIENT_DEVICE)


def sock_read_bytes(sock,total_bytes):
    data = b''
    remained_len = total_bytes
    while remained_len != 0:
        rx_data = sock.recv(remained_len)
        remained_len -= len(rx_data)
        data += rx_data
    return data


def get_soc_status():
    soc_status = dev.ctrl_transfer(SOC_STAT_REQ,GET_SOC_STAT,data_or_wLength = 1)
    return True if soc_status[0] == 0 else False


def get_pwr_status():
    pwr_status = dev.ctrl_transfer(PWR_STATUS_REQ,GET_PWR_STATUS,data_or_wLength = 1)
    return True if pwr_status[0] == 1 else False


def ep2_not_empty():
    epstat = dev.ctrl_transfer(EPSTAT_REQ, GET_EP2468STAT, data_or_wLength=1)
    not_empty = epstat[0] & 1 == 0
    if(not_empty):
        print('ep2_not_empty')
    return not_empty


def ep8_not_empty():
    epstat = dev.ctrl_transfer(EPSTAT_REQ,GET_EP2468STAT,data_or_wLength = 1)
    not_empty = epstat[0] & 0x40 == 0
    if(not_empty):
        print('ep8_not_empty')
    return not_empty

def scope_data_fetch():
    scope_data = b''
    i = 0
    while i < 8:
        if ep2_not_empty():
            scope_data += ep2.read(1024)
            print('%d'% len(scope_data))
            i += 1
    return scope_data


def config_dev():
    OP_MODE_IDLE = 0
    OP_MODE_SCAN = 1
    OP_MODE_CONTINUOUS = 2
    OP_MODE_SINGLE = 3
    if app_cfg['run']==True and app_cfg['single_trigger']==False:
        operation_mode = OP_MODE_CONTINUOUS
    elif app_cfg['run'] == True and app_cfg['single_trigger']==True:
        operation_mode = OP_MODE_SINGLE
    else:
        operation_mode = OP_MODE_IDLE
    scope_cfg = {
        0:bitstruct.pack('b1u1p1u13',app_cfg['auto_trigger'],app_cfg['trigger_source'],app_cfg['trigger_position']),
        1:bitstruct.pack('u6u2u8',app_cfg['clk_div'],app_cfg['trigger_mode'],app_cfg['trigger_level']),
        2:bitstruct.pack('p14u2',operation_mode),
        3:bitstruct.pack('u2u2u8p4',0,1,app_cfg['cha_vga']),
        4:bitstruct.pack('u2u2u8p4',1,1,app_cfg['chb_vga']),
        5:bitstruct.pack('p7u1u1p5b1b1',0,0,app_cfg['chb_ac'],app_cfg['cha_ac']),
    }
    ep8_data = b''
    for i in scope_cfg:
        ep8_data += bitstruct.byteswap('2',scope_cfg[i]) + struct.pack('BB',i,1)
    ep8.write(ep8_data)


def dev_handler():
    global dev,cfg,intf,ep2,ep8
    global app_cfg,cfg_updated
    global lock
    global conn
    cha_data = b''
    chb_data = b''
    while True:
        #print('loop')
        lock.acquire()
        if cfg_updated:
            cfg_updated = False
            config_dev()
        lock.release()

        # while ep8_not_empty():
        #
        #     if ep2_not_empty():
        #         print('useless data')
        #         scope_data_fetch()
        #         print('useless data abandon')
        if ep2_not_empty():
            header_array = ep2.read(4)
            scope_data = scope_data_fetch()
            (header,) = struct.unpack('>I', header_array)
            if header == CHA_FLAG :
                cha_data = scope_data
            elif header == CHB_FLAG:
                chb_data = scope_data
            else:
                print('unexpected flag received ' + str(header_array))
                debug_data = scope_data_fetch()
                print(str(debug_data))
                input()
        if cha_data != b'' and chb_data != b'':
            upload_data = struct.pack('!L',len(cha_data) + len(chb_data))
            upload_data +=  cha_data + chb_data
            conn.sendall(upload_data)
            print('data sent')
            cha_data = b''
            chb_data = b''


backend = usb.backend.libusb1.get_backend(lambda x: "libusb-1.0.dll")
dev = None
while dev is None:
    dev = usb.core.find(idVendor=0x04b4, idProduct=0x1004)
dev.set_configuration()
cfg = dev.get_active_configuration()
intf = cfg[0,0]
ep2 = intf[0]
ep8 = intf[1]

if(not get_pwr_status()):
    dev.ctrl_transfer(PWR_SWITCH_REQ,POWER_ON_OFF)
dev.ctrl_transfer(FPGA_GLOBAL_RST_REQ,FPGA_GLOBAL_RST)
time.sleep(1)
while(get_soc_status()==False):
    print('wait for soc status')
    pass
dev.ctrl_transfer(RST_EP2_BUF_REQ,RST_EP2_BUF)

app_cfg = {"trigger_level":3,"trigger_position":2,"single_trigger":False,
           "run":False,"trigger_source":1,"trigger_mode":0,"auto_trigger":True,
           "cha_ac":False,"chb_ac":False,"cha_vga":4,"chb_vga":5,"clk_div":6}

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind((HOST,PORT))
s.listen(0)
print('started')
conn,addr = s.accept()
print('accept: ' +str(addr))
app_cfg_json = json.dumps(app_cfg)
cfg_str = struct.pack('!L',len(app_cfg_json))
cfg_str += str.encode(app_cfg_json)
conn.sendall(cfg_str)
sock_in = [conn]
print('sended')
cfg_updated = True
lock = threading.Lock()
t1 = threading.Thread(target=dev_handler)
t1.start()

while True:

    ready_in,ready_out,ready_err = select.select(sock_in,[],[],0)
    if ready_in==[]:
        continue
    data_len_str = sock_read_bytes(ready_in[0],4)
    data_len = struct.unpack('!I', data_len_str)[0]
    data =  sock_read_bytes(ready_in[0],data_len)
 #   print('new cfg')
    try:
        lock.acquire()
        app_cfg = json.loads(data)
        cfg_updated = True
        lock.release()
    except:
        print(data)


